#pragma once

/*
 * service discovery module for c++ using redis.
 * which is for connection each other for nodes.
 */

#include <cstdarg>
#include <functional>
#include <string>
#include <vector>
#include <assert.h>
#include "time_wheel.h"
#include "dmcppredis.h"
#include "asio.hpp"

namespace anet {
	namespace ServiceDiscoverySpace {
		// service discovery log interface.
		class IServiceDiscoveryLog {
		public:
			virtual ~IServiceDiscoveryLog() {}
			// log interface.
			virtual bool setLevel(int level) = 0;
			virtual int  getLevel() const = 0;
			virtual void Debug(const char *format, ...) = 0;
			virtual void Info(const char *format, ...) = 0;
			virtual void Warn(const char *format, ...) = 0;
			virtual void Crit(const char *format, ...) = 0;
		};

		// global log variable reference.
		extern IServiceDiscoveryLog* gLog;
		extern std::atomic_bool gOpen;
		inline void SetLog(IServiceDiscoveryLog *log) {
			if (log != nullptr) {
				gLog = log;
			}
		}

		inline void SetLogFlag(bool open) {
			gOpen = open;
		}

		// service entity information.
		struct ServiceEntityInfo {
			// represent a entity's identity.
			std::string name;

			// entity's end point(ip:port).
			std::string endpoint;

			// attach data for extension.
			std::string attach;
		};

		// using list.
		using uint32 = unsigned int;
		using uint16 = unsigned short;
		using uint64 = unsigned long long;
		using int32 = int;
		using int16 = short;
		using int64 = long long;

		// new service discovery callback function when discoveries a new node coming.
		// this function is called in a new thread.
		// successfully return true, else return false, try to remove it if it is false.
		using newServiceDiscoveryFunc = std::function<bool(const ServiceEntityInfo *newNode)>;

		// macros list.
		static constexpr uint32 gPublishMaxCount = 5;

		// subscribe timer id.
		static constexpr uint32 gSubscribeTimerId = 0;

		// subscribe interval: 5s.
		static constexpr uint32 gSubscribeTimeInterval = 5 * 1000;

		// timer size.
		static constexpr uint32 gTimerWheelSize = 127;

		// redis connection time out: 10s.
		static constexpr uint32 gRedisConnectTimeout = 10 * 1000;

		// default redis key.
		static const char *gRedisServiceKey = "serviceDiscovery@PublicKey";

		// service discovery module.
		class ServiceDiscovery final : asio::noncopyable {
		public:
			ServiceDiscovery(const std::string& name, 
				const std::string& endpoint,
				const std::string& attach, 
				newServiceDiscoveryFunc newServiceDiscovery
			);
			ServiceDiscovery(const ServiceEntityInfo& entityInfo, 
				newServiceDiscoveryFunc newServiceDiscovery
			);
			virtual ~ServiceDiscovery();

		public:
			// initialize the ServiceDiscovery.
			// redis address, serverIP:serverPort
			// publishInterval is publish interval, unit: s.
			// redisKey is redis key.
			bool Init(std::string serverIP, uint16 serverPort,
				uint32 publishInterval = gSubscribeTimeInterval,
				const std::string& redisKey = gRedisServiceKey
			);

			// update timer.
			void Run();

			// Release releases all my resources.
			void Release();

			// RemoveService removes service name from my node list.
			bool RemoveService(const std::string& name);

			// FindService is whether there is name node in my node list.
			bool FindService(const std::string& name) const;

		protected:
			// subscribe subscribes from redis to get new service node.
			void subscribe();

			// publish publishes myself entity information to redis.
			bool publish();

			// timer's callback function.
			void timerDo();

			// internal remove service which has no lock.
			bool removeService(const std::string& name);

		protected:
			using serviceEntityVec = std::vector<ServiceEntityInfo>;
			using redisClient = cpp_redis::client;

			// user callback interface, which is executed in the thread.
			newServiceDiscoveryFunc m_newServiceDiscoveryFunc{ nullptr };

			// service all entities.
			serviceEntityVec m_serviceEntities;

			// self entity info
			ServiceEntityInfo m_selfEntity;

			// mutex for m_serviceEntities
			mutable std::mutex m_mutex;

			// redis key.
			std::string m_serviceDiscoveryKey;

			// timer information.
			STimeWheelSpace::CTimeWheel m_wheel;
			STimeWheelSpace::CTimerRegister m_timer;

			// redis module.
			redisClient m_redisClient;

			// current subscribe count.
			uint32 m_subscribeCount{ 0 };

			// io_context, work and thread.
			typedef std::unique_ptr<asio::io_context> ioContextPtr;
			typedef std::unique_ptr<asio::io_context::work> workPtr;
			typedef std::unique_ptr<std::thread> threadPtr;

			// io_context and thread to execute the service discovery publish and subscribe.
			ioContextPtr m_ioContext{ nullptr };
			workPtr m_worker{ nullptr };
			threadPtr m_th{ nullptr };
		};
	}
}